﻿using System;
using System.Collections.Generic;
using System.Linq;
using BL.Model.Request;
using Common;
using DAL.Model;

namespace BL
{
    public class StockOutController
    {
        private readonly MasterSession _session;
        private readonly InventoryController _inventoryController;
        private readonly LiabilitiesController _liabilitiesController;
        private readonly DocumentSeqNoHelper _documentSeqNoHelper;
        
        public StockOutController(MasterSession session)
        {
            _session = session;
            _inventoryController = new InventoryController(_session);
            _liabilitiesController = new LiabilitiesController(_session);
            _documentSeqNoHelper = new DocumentSeqNoHelper("StockOut", _session);
        }

        public StockOut Create()
        {
            var stockOut = new StockOut
                               {
                                   DocumentNo = _documentSeqNoHelper.GetNextDocSeqNo(),
                                   StockedDate = DateTime.Now
                               };
            _session.StockOuts.Add(stockOut);

            return stockOut;
        }

        public StockOut GenStockOutFromTransfers (int[] transferIds)
        {
            var transfers = _session.Transfers
                .Where(t => transferIds.Contains(t.Id))
                .ToArray();
            var stockIns = transfers.Select(t => t.StockIn).ToArray();
            
            StockOut stockOut = null;
            decimal totalQuantity = 0;

            // get stockout qty dictionary
            var stockInIds = stockIns.Select(t => t.Id).ToArray();
            var stockOutMassQtys = _session.StockOuts
                .Where(t => stockInIds.Contains(t.StockInId))
                .SelectMany(t => t.StockOutDetails)
                .GroupBy(t => new
                                  {
                                      t.ProductId,
                                      t.Specifical
                                  })
                .ToDictionary(t => t.Key, t => t.Sum(v => v.MassQuantity));
            var stockOutQtys = _session.StockOuts
                .Where(t => stockInIds.Contains(t.StockInId))
                .SelectMany(t => t.StockOutDetails)
                .GroupBy(t => new
                                  {
                                      t.ProductId,
                                      t.Specifical
                                  })
                .ToDictionary(t => t.Key, t => t.Sum(v => v.Quantity));
            var lineNo = 1;
            foreach (var transfer in transfers)
            {
                foreach(var transferDetail in transfer.TransferDetails
                    .Where(t => !t.IsOutOfStock.HasValue || !(bool) t.IsOutOfStock))
                {
                    var stockoutMassQty = stockOutMassQtys
                        .FirstOrDefault(t => Equals(t.Key,
                                                    new
                                                        {
                                                            transferDetail.ProductId,
                                                            transferDetail.Specifical
                                                        }))
                        .Value;
                    var stockoutQty = stockOutQtys
                        .FirstOrDefault(t => Equals(t.Key,
                                                    new
                                                        {
                                                            transferDetail.ProductId,
                                                            transferDetail.Specifical
                                                        }))
                        .Value;

                    if ((transferDetail.MassQuantity + stockoutMassQty) > 0 && 
                        stockoutMassQty >= transferDetail.MassQuantity &&
                        (transferDetail.Quantity + stockoutQty) > 0 && 
                        stockoutQty >= transferDetail.Quantity)
                    {
                        continue;
                    }
                    if (stockOut == null)
                    {
                        stockOut = Create();
                        stockOut.Transfer = transfer;
                        stockOut.StockIn = transferDetail.StockInDetail.StockIn;
                        stockOut.Customer = transfer.Customer;
                        stockOut.Warehouse = transfer.Warehouse1;
                        stockOut.ContactName = transfer.Customer.ContactName;
                        stockOut.StockedDate = DateTime.Now;
                    }

                    var stockOutDetail = CreateDetail(stockOut);
                    stockOutDetail.LineNo = lineNo++;
                    stockOutDetail.TransferDetail = transferDetail;
                    stockOutDetail.Product = transferDetail.Product;
                    stockOutDetail.ProductId = transferDetail.Product.Id;
                    stockOutDetail.StockInQuantity = transferDetail.StockInQuantity;
                    stockOutDetail.MassQuantity = transferDetail.MassQuantity > 0
                                                      ? transferDetail.MassQuantity - stockoutMassQty
                                                      : 0;
                    stockOutDetail.Quantity = transferDetail.Quantity > 0
                                                  ? transferDetail.Quantity - stockoutQty
                                                  : 0;
                    stockOutDetail.Specifical = transferDetail.Specifical;
                    stockOutDetail.UnitOfMeasure = transferDetail.UnitOfMeasure;
                    stockOutDetail.Note = transferDetail.Note;
                    stockOutDetail.Note2 = transferDetail.Note2;
                    totalQuantity += stockOutDetail.MassQuantity;

                    stockOut.TotalQuantity = totalQuantity;
                }
            }
            return stockOut;
        }

        public StockOut GenStockOutFromStockIns(int[] stockInIds)
        {
            StockOut stockOut = null;
            decimal totalQuantity = 0;

            // get stockin objects
            var stockIns = _session.StockIns
                .Where(t => stockInIds.Contains(t.Id))
                .ToArray();

            // get stockout qty dictionary
            var stockOutMassQtys = _session.StockOuts
                .Where(t => stockInIds.Contains(t.StockInId))
                .SelectMany(t => t.StockOutDetails)
                .GroupBy(t => t.ProductId)
                .ToDictionary(t => t.Key, t => t.Sum(v => v.MassQuantity));

            var lineNo = 1;
            foreach (var stockIn in stockIns)
            {
                foreach (var stockInDetail in stockIn.StockInDetails)
                    //.Where(t => !t.IsOutOfStock.HasValue || !(bool)t.IsOutOfStock))
                {
                    var stockoutMassQty = stockOutMassQtys
                        .FirstOrDefault(t => Equals(t.Key,stockInDetail.ProductId))
                        .Value;

                    var massQuantity = stockInDetail.Quantity > 0
                                           ? stockInDetail.Quantity - stockoutMassQty
                                           : 0;
                    if (massQuantity <= 0)
                    {
                        continue;
                    }

                    if (stockOut == null)
                    {
                        stockOut = Create();
                        stockOut.StockIn = stockIn;
                        stockOut.Customer = stockIn.Customer;
                        stockOut.ContactName = stockIn.Customer.ContactName;
                        stockOut.StockedDate = DateTime.Now;
                    }

                    var stockOutDetail = CreateDetail(stockOut);
                    stockOutDetail.LineNo = lineNo++;
                    stockOutDetail.Product = stockInDetail.Product;
                    stockOutDetail.ProductId = stockInDetail.Product.Id;
                    stockOutDetail.StockInQuantity = stockInDetail.Quantity;
                    stockOutDetail.MassQuantity = massQuantity;
                    stockOutDetail.Quantity = 0;
                    stockOutDetail.IsOutOfStock = true;
                    stockOutDetail.Specifical = "Xuất trả cuộn";
                    stockOutDetail.UnitOfMeasure = stockInDetail.UnitOfMeasure;
                    stockOutDetail.Note = stockInDetail.Note;
                    stockOutDetail.Note2 = stockInDetail.Note2;
                    totalQuantity += stockOutDetail.MassQuantity;

                    stockOut.TotalQuantity = totalQuantity;
                }
            }
            return stockOut;
        }

        public StockOutDetail CreateDetail(StockOut stockOut)
        {
            var stockOutDetail = new StockOutDetail
                                     {
                                         StockOut = stockOut
                                     };
            _session.StockOutDetails.Add(stockOutDetail);

            return stockOutDetail;
        }

        public void PrepareSavingData(StockOut stockOut)
        {
            if (stockOut == null)
            {
                return;
            }

            // check for warehouse
            if (stockOut.Warehouse == null)
            {
                var inWarehouse = _session.Warehouses
                    .FirstOrDefault(t => t.Id == Constant.StockInWarehouseId);
                stockOut.Warehouse = inWarehouse;
            }
            // merge inventory data
            decimal totalQuantity = 0;
            decimal totalPayment = 0;

            // delete old liabilities and inventories
            if (stockOut.Liabilities.Count > 0)
            {
                _session.Liabilities.RemoveRange(stockOut.Liabilities);
            }
            if (stockOut.Inventories.Count > 0)
            {
                _session.Inventories.RemoveRange(stockOut.Inventories);
            }

            // get all previous stockout details of product 
            // from current stockout
            var productIds = stockOut.StockOutDetails
                .Select(t => t.ProductId)
                .Distinct()
                .ToArray();

            List<StockOutDetail> stockOutDetails;
            if (stockOut.Id == 0) // if new object, get from memory
            {
                stockOutDetails = _session.StockOutDetails
                    .Local
                    .Where(t => //specifical.Contains(t.Specifical) &&
                                productIds.Contains(t.ProductId))
                    .ToList();
            }
            else
            {
                stockOutDetails = _session.StockOutDetails
                    .Where(t => //specifical.Contains(t.Specifical) &&
                                productIds.Contains(t.ProductId))
                    .ToList();
            }

            // reset qty from transfer and stockin
            var stockinDetails = _session.StockInDetails
                .Where(t => productIds.Contains(t.ProductId)).ToList();
            stockOutDetails.ForEach(t =>
                                        {
                                            if (t.TransferDetail == null) return;
                                            t.TransferDetail.StockOutMassQuantity = 0;
                                            t.TransferDetail.StockOutQuantity = 0;
                                            if (t.TransferDetail.StockInDetail != null)
                                            {
                                                t.TransferDetail.StockInDetail.StockOutMassQuantity = 0;
                                                t.TransferDetail.StockInDetail.StockOutQuantity = 0;
                                            }
                                        });

            // calculate stockout qty by specifical
            var stockOutMassQtysTF = stockOutDetails
                .GroupBy(t => new { t.ProductId, t.Specifical })
                .ToDictionary(t => t.Key, t => t.Sum(v => v.MassQuantity));
            var stockOutQtysTF = stockOutDetails
                .GroupBy(t => new { t.ProductId, t.Specifical })
                .ToDictionary(t => t.Key, t => t.Sum(v => v.Quantity));

            // update stockout qty to transfer
            var transferDetails = stockOutDetails
                .Select(t => t.TransferDetail)
                .Distinct()
                .ToArray();
            foreach (var transferDetail in transferDetails.Where(t => t != null))
            {
                var transferMassQty = stockOutMassQtysTF
                    .FirstOrDefault(t => Equals(t.Key,
                                                new
                                                    {
                                                        transferDetail.ProductId,
                                                        transferDetail.Specifical
                                                    }))
                    .Value;
                var transferQty = stockOutQtysTF
                    .FirstOrDefault(t => Equals(t.Key,
                                                new
                                                {
                                                    transferDetail.ProductId,
                                                    transferDetail.Specifical
                                                }))
                    .Value;
                transferDetail.StockOutMassQuantity += transferMassQty;
                transferDetail.StockOutQuantity += transferQty;
            }
            // calculate stockout qty by product Id
            var stockOutMassQtysSI = stockOutDetails
                .GroupBy(t => t.ProductId)
                .ToDictionary(t => t.Key, t => t.Sum(v => v.MassQuantity));
            var stockOutQtysSI = stockOutDetails
                .GroupBy(t => t.ProductId)
                .ToDictionary(t => t.Key, t => t.Sum(v => v.Quantity));

            // update stockout qty to stockin
            foreach(var stockinDetail in stockinDetails)
            {
                var stockOutMassQty = stockOutMassQtysSI
                    .FirstOrDefault(t => t.Key == stockinDetail.ProductId)
                    .Value;
                var stockOutQty = stockOutQtysSI
                    .FirstOrDefault(t => t.Key == stockinDetail.ProductId)
                    .Value;
                stockinDetail.StockOutMassQuantity += stockOutMassQty;
                stockinDetail.StockOutQuantity += stockOutQty;
            }

            TransferDetail lastTransfer = null;
            foreach (var stockOutDetail in stockOut.StockOutDetails.OrderBy(t=>t.LineNo))
            {
                var amount = stockOutDetail.Amount;

                #region Obsoleted: Handle "keo"
                // tinh toan cho quy cach xuat co keo
                // bo qua phan nay vi cho phep nhap khoi luong keo am tren form
                //if (stockOutDetail.IsAccumulated != null)
                //{
                //    // find next lineNo with specifical contains "keo"
                //    var lineNo = stockOutDetail.LineNo;
                //    var soKeo = stockOut.StockOutDetails
                //        .FirstOrDefault(t => Equals(t.LineNo, lineNo + 1) &&
                //                             t.Product.Id == stockOutDetail.Product.Id &&
                //                             t.Specifical.ToLower().Contains("keo"));
                //    if (soKeo == null)
                //    {
                //        return;
                //    }
                //    // Quy cach xuat nay da co keo, 
                //    // tim trong cac SO detail lay ra dong co chu 'keo'
                //    // lay so luong keo xuat nay tru voi so luong xuat cua quy cach
                //    soKeo.Accumulated = true;
                //    massQuantity = stockOutDetail.MassQuantity;
                //    amount = stockOutDetail.Amount;
                //}
                //if (!string.IsNullOrEmpty(stockOutDetail.Specifical) &&
                //    stockOutDetail.Specifical.ToLower().Contains("keo"))
                //{
                //    // find previous lineNo with checked "Da co keo"
                //    var lineNo = stockOutDetail.LineNo;
                //    var sodAccumulated = stockOut.StockOutDetails
                //        .FirstOrDefault(t => Equals(t.LineNo, lineNo - 1) &&
                //                             t.Product.Id == stockOutDetail.Product.Id &&
                //                             (t.IsAccumulated ?? false));
                //    if (sodAccumulated == null)
                //    {
                //        return;
                //    }
                //    massQuantity = stockOutDetail.MassQuantity * (-1);
                //    amount = ((stockOutDetail.MassQuantity > 0
                //                    ? stockOutDetail.MassQuantity
                //                    : stockOutDetail.Quantity)
                //               * stockOutDetail.Price) * (-1);
                //}
                #endregion

                // update stockout qty
                if (stockOutDetail.TransferDetail != null &&
                    lastTransfer != stockOutDetail.TransferDetail)
                {
                    stockOutDetail.TransferDetail.IsOutOfStock = stockOutDetail.IsOutOfStock;
                }
                lastTransfer = stockOutDetail.TransferDetail;

                // update inventories
                var inventory = _inventoryController.Create();
                inventory.LineNo = stockOutDetail.LineNo;
                inventory.StockOut = stockOut;
                inventory.Customer = stockOut.Customer;
                inventory.Warehouse = stockOut.Warehouse;
                inventory.IssuedDate = stockOut.StockedDate;
                inventory.Product = stockOutDetail.Product;
                inventory.StockOutQty = stockOutDetail.MassQuantity;
                inventory.AdjustQuantity = stockOutDetail.Quantity;
                inventory.UnitOfMeasure = stockOutDetail.UnitOfMeasure;
                inventory.UnitPrice = stockOutDetail.Price;
                inventory.Amount = amount;
                inventory.Specifical = stockOutDetail.Specifical;
                inventory.Note = stockOutDetail.Note;
                inventory.Note2 = stockOutDetail.Note2;

                // update liabities
                var liability = _liabilitiesController.Create();
                liability.StockOut = stockOut;
                liability.StockOutQty = stockOutDetail.MassQuantity;
                liability.UnitPrice = stockOutDetail.Price;
                liability.IssuedDate = stockOut.StockedDate;
                liability.Customer = stockOut.Customer2 ?? stockOut.Customer;
                liability.LineNo = stockOutDetail.LineNo;
                liability.Spend = amount;
                liability.Description = stockOutDetail.Specifical;
                liability.Accumulated = stockOutDetail.Accumulated;
                liability.Discount = stockOutDetail.Discount;
                liability.Note = stockOutDetail.Note;
                liability.Note2 = stockOutDetail.Note2;

                totalQuantity += stockOutDetail.MassQuantity;
                totalPayment += amount;
            }

            stockOut.TotalQuantity = totalQuantity;
            stockOut.TotalPayment = totalPayment;

            _documentSeqNoHelper.UpdateDocSeqNo(stockOut.DocumentNo);
        }
        
        /// <summary>
        /// Search stock out by keyword with ordering
        /// </summary>
        /// <param name="request"></param>
        /// <returns>List of stock out</returns>
        /// <history>
        /// 12/30/2014 phat.vo: Create new
        /// </history>
        public List<StockOut> Search(SearchingStockOutRequest request)
        {
            var isAllDocumentNo = string.IsNullOrWhiteSpace(request.DocumentNo);
            var documentNo = isAllDocumentNo ? string.Empty : request.DocumentNo.Trim().ToLower();

            var isAllProductCode = string.IsNullOrWhiteSpace(request.ProductCode);
            var productCode = isAllProductCode ? string.Empty : request.ProductCode.Trim().ToLower();

            var isAllCustomer = request.CustomerIds == null || request.CustomerIds.Contains(Constant.AllValue);
            var customerIds = isAllCustomer ? new List<int>() : request.CustomerIds;

            var isAllTransferNo = string.IsNullOrWhiteSpace(request.TransferNo);
            var transferNo = isAllTransferNo ? string.Empty : request.TransferNo.Trim().ToLower();

            var isAllProductCategory = request.ProductCategoryIds == null ||
                                       request.ProductCategoryIds.Contains(Constant.AllValue);
            var productCategoryIds = isAllProductCategory ? new List<int>() : request.ProductCategoryIds;

            var isAllStockInNo = string.IsNullOrWhiteSpace(request.StockInNo);
            var stockInNo = isAllStockInNo ? string.Empty : request.StockInNo.Trim().ToLower();

            var query =
                (from stockOut in _session.StockOuts
                 where (isAllDocumentNo || stockOut.DocumentNo.ToLower() == documentNo.ToLower()) &&
                       (isAllTransferNo || stockOut.Transfer.DocumentNo.ToLower() == transferNo.ToLower()) &&
                       (isAllStockInNo || stockOut.StockIn.DocumentNo.ToLower() == stockInNo.ToLower()) &&
                       (isAllProductCode ||
                        stockOut.StockOutDetails.Any(detail => detail.Product.Code.ToLower() == productCode.ToLower())) &&
                       (isAllProductCategory || stockOut.StockOutDetails
                                                    .Any(detail =>
                                                         detail.Product.ProductCategoryId.HasValue &&
                                                         productCategoryIds.Contains(
                                                             detail.Product.ProductCategoryId.Value))) &&
                       (!request.FromTime.HasValue || request.FromTime <= stockOut.StockedDate) &&
                       (!request.ToTime.HasValue || request.ToTime >= stockOut.StockedDate) &&
                       (isAllCustomer || customerIds.Contains(stockOut.CustomerId))
                 select stockOut)
                    .OrderByDescending(t => t.StockedDate);

            return query.ToList();
        }
    }
}
